package luj.tool.jnproxyan.proxy.network.https.keystore;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;

import java.math.BigInteger;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.Date;

public class CertDynamicIssuer {

  public CertDynamicIssuer(PrivateKey caPrivate, X509Certificate caCert, PublicKey issuedPublic, String addr) {
    _caPrivate = caPrivate;
    _caCert = caCert;
    _issuedPublic = issuedPublic;
    _addr = addr;
  }

  public X509Certificate issue() throws Exception {
    // Setup start date to yesterday and end date for 1 year validity
    Calendar calendar = Calendar.getInstance();
    calendar.add(Calendar.DATE, -1);
    Date startDate = calendar.getTime();
    calendar.add(Calendar.YEAR, 1);
    Date endDate = calendar.getTime();

    // Generate a new KeyPair and sign it using the Root Cert Private Key
    // by generating a CSR (Certificate Signing Request)
    X500Name issuedCertSubject = new X500Name("CN=" + _addr);
    BigInteger issuedCertSerialNum = new BigInteger(Long.toString(new SecureRandom().nextLong()));

    PKCS10CertificationRequestBuilder p10Builder = new JcaPKCS10CertificationRequestBuilder(issuedCertSubject, _issuedPublic);
    JcaContentSignerBuilder csrBuilder = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(BC_PROVIDER);

    // Sign the new KeyPair with the root cert Private Key
    ContentSigner csrContentSigner = csrBuilder.build(_caPrivate);
    PKCS10CertificationRequest csr = p10Builder.build(csrContentSigner);

    // Use the Signed KeyPair and CSR to generate an issued Certificate
    // Here serial number is randomly generated. In general, CAs use
    // a sequence to generate Serial number and avoid collisions
    X500Name rootCertIssuer = new X500Name(_caCert.getIssuerX500Principal().getName());
    X509v3CertificateBuilder issuedCertBuilder = new X509v3CertificateBuilder(rootCertIssuer, issuedCertSerialNum, startDate, endDate, csr.getSubject(), csr.getSubjectPublicKeyInfo());

    JcaX509ExtensionUtils issuedCertExtUtils = new JcaX509ExtensionUtils();

    // Add Issuer cert identifier as Extension
    issuedCertBuilder.addExtension(Extension.authorityKeyIdentifier, false, issuedCertExtUtils.createAuthorityKeyIdentifier(_caCert));
    issuedCertBuilder.addExtension(Extension.subjectKeyIdentifier, false, issuedCertExtUtils.createSubjectKeyIdentifier(csr.getSubjectPublicKeyInfo()));

    // Add DNS name is cert is to used for SSL
    issuedCertBuilder.addExtension(Extension.subjectAlternativeName, false, new DERSequence(new ASN1Encodable[]{
        new GeneralName(GeneralName.dNSName, _addr),
//        new GeneralName(GeneralName.iPAddress, "127.0.0.1"),
    }));

    X509CertificateHolder issuedCertHolder = issuedCertBuilder.build(csrContentSigner);
    X509Certificate issuedCert = new JcaX509CertificateConverter().setProvider(BC_PROVIDER).getCertificate(issuedCertHolder);

//    // Verify the issued cert signature against the root (issuer) cert
//    issuedCert.verify(_caCert.getPublicKey(), BC_PROVIDER);

    return issuedCert;
  }

  private static final Provider BC_PROVIDER = new BouncyCastleProvider();
  private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";

  private final PrivateKey _caPrivate;
  private final X509Certificate _caCert;

  private final PublicKey _issuedPublic;
  private final String _addr;
}
